/********************************************
 * MODUL
 * 
 * Name: TCP_SOCKET_HELPER
 * 
 * Zweck: Fasst die Funktionen zusammen, die zum Aufbau einer TCP Socket Verbindung dienen
 * 
 * Autor(en): Simon Hinderhofer, Robert Lehmann, Benjamin Reiner, TIT08
 * 
 * Quellen: Diese Funktionen wurden frei nach den folgenden Quelle erstellt:
 *	    http://www.linuxhowtos.org/C_C++/socket.htm
 * 	    http://www.cs.cf.ac.uk/Dave/C/node28.html
 * 	    http://www.pc-adviser.de/socket_programmierung.html#tricks
 * 	    Folien der Vorlesung
 * 
 ********************************************/


/* für input und output */
#include <stdio.h>
/* für unlink, read, write, close */
#include <unistd.h>
/* definitionen benötigter datentypen */
#include <sys/types.h> 
/* definitionen und strukturen für sockets */
#include <sys/socket.h>
/* konstanten und strukturen die für die definition von internetadressen */
#include <netinet/in.h>
/* Definiert die Struktur hostent */
#include <netdb.h>
/* für IPC */
#include <sys/un.h>
/* für die Ausgabe in die Datei oder auf den Bildschirm */
#include "hex_print.h"
/* um die sockets non blocking zu machen */
#include <fcntl.h>
/* für time() */
#include <time.h>
/* für errno */
#include <errno.h>
/* für inet_ntoa */
#include <arpa/inet.h>

#include "tcp_socket_helper.h"

/********************************************
 * FUNKTION
 * 
 * Name: error(const char *msg)
 * 
 * Zweck: Fehlerbehandlung, gibt den fehler auf stderr aus
 * 
 * Parameter:
 * 	const char *msg: INPUT, eine fehlernachricht
 * 
 * Globale Variablen:
 * 	keine
 * 
 * Rückgabewert:
 * 	nicht vorhanden
 * 
 ********************************************/
void error(const char *msg)
{
  /* gibt den fehler auf stderr aus */
  perror(msg);
}

/********************************************
 * FUNKTION
 * 
 * Name: server_create_tcp_socket(int socket_port)
 * 
 * Zweck: Erstellt einen TCP server socket und gibt dessen descriptor zurück
 * 
 * Parameter:
 * 	int socket_port: INPUT, port auf dem der socket angelegt werden soll
 * 
 * Globale Variablen:
 * 	keine
 * 
 * Rückgabewert:
 * 	socket descriptor des erstellten sockets
 * 	oder -1 im fehlerfall
 * 
 ********************************************/
int server_create_tcp_socket(int socket_port)
{
  int server_socket;
  struct sockaddr_in serv_addr;
  
  /* erstelle einen neuen Socket */
  server_socket = socket(AF_INET, SOCK_STREAM, 0);
  if (server_socket < 0)
  { 
    error("FEHLER beim Erstellen des Sockets");
    return -1;
  }
  
  /* initialisiere die Serveradresse mit Nullen */
  bzero((char *) &serv_addr, sizeof(serv_addr));
  
  /* Adressfamilie */
  serv_addr.sin_family = AF_INET;
  /* Port in big endian */
  serv_addr.sin_port = htons(socket_port);
  /* host ip bei einem server immer INADDR_ANY konstante */
  serv_addr.sin_addr.s_addr = INADDR_ANY;
  
  /* socket an die Adresse binden */
  if (bind(server_socket, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
  {
    error("FEHLER beim Binden des Sockets");
    return -1;
  }
  
  /* auf Socketverbindungen horchen */
  /* zweiter Parameter gibt an, wie viele Verbindungen warten dürfen
   * solange der Socket mit einer Verbindung beschäftigt ist. Da die
   * meisten Systeme auf 5 limitieren, ist dieser Wert auch so gewählt */
  listen(server_socket,5);
  
  return server_socket; 
}

/********************************************
 * FUNKTION
 * 
 * Name: server_accept_tcp_socket(int server_socket)
 * 
 * Zweck: Wartet auf einen verbindenen client und akzeptiert diesen
 * 
 * Parameter:
 * 	int server_socket: INPUT, socket, auf den der client verbindet
 * 
 * Globale Variablen:
 * 	keine
 * 
 * Rückgabewert:
 * 	socket descriptor des neu akzeptierten clients
 * 	oder -1 im fehlerfall
 * 
 ********************************************/
int server_accept_tcp_socket(int server_socket)
{
  socklen_t clilen;
  struct sockaddr_in cli_addr;
  int client_socket;
  
  clilen = sizeof(cli_addr);
  client_socket = accept(server_socket, (struct sockaddr *) &cli_addr, &clilen);
  if (client_socket < 0)
  {
    error("FEHLER beim Akzeptieren");
    return -1;
  }
  return client_socket;
}

/********************************************
 * FUNKTION
 * 
 * Name: server_create_tcp_ipc_socket(char* socket_adress)
 * 
 * Zweck: Erstellt einen TCP server socket zur interprozesskommunikation und gibt dessen descriptor zurück
 * 
 * Parameter:
 * 	char* socket_adress: INPUT, unix adresse auf der der socket angelegt werden soll
 * 
 * Globale Variablen:
 * 	keine
 * 
 * Rückgabewert:
 * 	socket descriptor des erstellten sockets
 * 	oder -1 im fehlerfall
 * 
 ********************************************/
int server_create_tcp_ipc_socket(char* socket_adress)
{
  int server_socket;
  struct sockaddr_un serv_addr;
  register int len;
  
  /* erstelle einen neuen Socket */
  server_socket = socket(AF_UNIX, SOCK_STREAM, 0);
  if (server_socket < 0)
  { 
    error("FEHLER beim Erstellen des Sockets");
    return -1;
  }
  
  /* Adressfamilie */
  serv_addr.sun_family = AF_UNIX;
  /* Adresse */
  strcpy(serv_addr.sun_path, socket_adress);
  
  /* zuerst unlinken, sonst kann das Binden fehlschlagen */
  unlink(socket_adress);
  len = sizeof(serv_addr.sun_family) + strlen(serv_addr.sun_path);
  
  /* socket an die Adresse binden */
  if (bind(server_socket, (struct sockaddr *) &serv_addr, len) < 0)
  {
    error("FEHLER beim Binden des Sockets");
    return -1;
  }
  
  /* auf Socketverbindungen horchen */
  /* zweiter Parameter gibt an, wie viele Verbindungen warten dürfen
   * solange der Socket mit einer Verbindung beschäftigt ist. Da die
   * meisten Systeme auf 5 limitieren, ist dieser Wert auch so gewählt */
  listen(server_socket,5);
  
  return server_socket; 
}

/********************************************
 * FUNKTION
 * 
 * Name: server_accept_tcp_ipc_socket(int server_socket)
 * 
 * Zweck: Wartet auf einen verbindenen client zur interprozesskommunikation und akzeptiert diesen
 * 
 * Parameter:
 * 	int server_socket: INPUT, socket, auf den der client verbindet
 * 
 * Globale Variablen:
 * 	keine
 * 
 * Rückgabewert:
 * 	socket descriptor des neu akzeptierten clients
 * 	oder -1 im fehlerfall
 * 
 ********************************************/
int server_accept_tcp_ipc_socket(int server_socket)
{
  socklen_t clilen;
  struct sockaddr_in cli_addr;
  int client_socket;
  
  clilen = sizeof(cli_addr);
  client_socket = accept(server_socket, (struct sockaddr *) &cli_addr, &clilen);
  if (client_socket < 0)
  {
    if (errno == EBADF)
    {
      /* der Fehlercode EBADF steht für "Bad file number"
       * Dieser tritt auf, wenn der socket geschlossen wird, während accept wartet.
       * Da dies auftritt, wenn ein proxy prozess beendet wird, wird hier auf fehlerlogging verzichtet */
    }
    else
    {
      error("FEHLER beim Akzeptieren");
    }
    return -1;
  }
  return client_socket;
}

/********************************************
 * FUNKTION
 * 
 * Name: server_create_tcp_socket(char* server_name, int socket_port)
 * 
 * Zweck: Erstellt einen TCP client socket und gibt dessen descriptor zurück
 * 
 * Parameter:
 * 	char* server_name: INPUT, name des servers, zu dem der socket erstellt werden soll
 * 	int socket_port: INPUT, port auf dem der socket angelegt werden soll
 * 
 * Globale Variablen:
 * 	keine
 * 
 * Rückgabewert:
 * 	socket descriptor des erstellten sockets
 * 	oder -1 im fehlerfall
 * 
 ********************************************/
int client_create_tcp_socket(char* server_name, int socket_port)
{
  int server_socket;
  struct sockaddr_in serv_addr;
  struct hostent *server;
  
  /* erstelle einen neuen Socket */
  server_socket = socket(AF_INET, SOCK_STREAM, 0);
  if (server_socket < 0)
  {
    error("FEHLER beim Erstellen des Sockets");
    return -1;
  }
  
  /* Finde die Adresse des Servers */
  server = gethostbyname(server_name);
  if (server == NULL) 
  {
    error("FEHLER Host wurde nicht gefunden");
    return -1;
  }
  
  /* initialisiere die Serveradresse mit Nullen */
  bzero((char *) &serv_addr, sizeof(serv_addr));
  
  /* Adressfamilie */
  serv_addr.sin_family = AF_INET;
  /* host ip muss mit bcopy vom server struct in das serv_addr struct kopiert werden */
  bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
  /* Port in big endian */
  serv_addr.sin_port = htons(socket_port);
  
  if (connect(server_socket,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0)
  {
    error("FEHLER beim Verbinden");
    return -1;
  }
  
  printf("Connected to host %s (%d.%d.%d.%d).\n\n", server_name, (unsigned char)server->h_addr[0], (unsigned char)server->h_addr[1], (unsigned char)server->h_addr[2], (unsigned char)server->h_addr[3]);
  
  return server_socket;
}

/********************************************
 * FUNKTION
 * 
 * Name: client_create_tcp_ipc_socket(char* server_name)
 * 
 * Zweck: Erstellt einen TCP client socket zur interprozesskommunikation und gibt dessen descriptor zurück
 * 
 * Parameter:
 * 	char* socket_adress: INPUT, unix adresse auf der der socket angelegt werden soll
 * 
 * Globale Variablen:
 * 	keine
 * 
 * Rückgabewert:
 * 	socket descriptor des erstellten sockets
 * 	oder -1 im fehlerfall
 * 
 ********************************************/
int client_create_tcp_ipc_socket(char* server_name)
{
  int server_socket;
  struct sockaddr_un serv_addr;
  register int len;
  
  /* erstelle einen neuen Socket */
  server_socket = socket(AF_UNIX, SOCK_STREAM, 0);
  if (server_socket < 0)
  {
    error("FEHLER beim Erstellen des Sockets");
    return -1;
  }
  
  /* Adressfamilie */
  serv_addr.sun_family = AF_UNIX;
  /* Adresse */
  strcpy(serv_addr.sun_path, server_name);
  
  len = sizeof(serv_addr.sun_family) + strlen(serv_addr.sun_path);
  
  if (connect(server_socket, (struct sockaddr *) &serv_addr, len) < 0)
  {
    error("FEHLER beim Verbinden");
    return -1;
  }
  
  return server_socket;
}

/********************************************
 * FUNKTION
 * 
 * Name: read_tcp_socket(int socket)
 * 
 * Zweck: liest bis zu 256 zeichen vom angegebenen socket
 * 
 * Parameter:
 * 	int socket: INPUT, socket, von dem gelesen werden soll
 * 
 * Globale Variablen:
 * 	keine
 * 
 * Rückgabewert:
 * 	ein zeiger, der auf die gelesenen daten zeigt
 * 	oder NULL im fehlerfall
 * 
 ********************************************/
char* read_tcp_socket(int socket)
{
  static char buffer[256];
  int char_read;
  
  /* Buffer initialisieren */
  bzero(buffer,256);
  
  /* Es wird von Socket in den Buffer gelesen
   * in char_read steht die anzahl der gelesenen Zeichen */
  char_read = read(socket,buffer,255);
  if (char_read < 0)
  {
    error("FEHLER beim Lesen");
    return NULL;
  }
  return buffer;
}

/********************************************
 * FUNKTION
 * 
 * Name: write_tcp_socket(int socket, char message[])
 * 
 * Zweck: schreibt angegebene zeichen auf den angegebenen socket
 * 
 * Parameter:
 * 	int socket: INPUT, socket, auf den geschrieben werden soll
 * 	char message[]: INPUT: die zu sendende nachricht
 * 
 * Globale Variablen:
 * 	keine
 * 
 * Rückgabewert:
 * 	anzahl der geschriebenen zeichen
 * 	oder -1 im fehlerfall
 * 
 ********************************************/
int write_tcp_socket(int socket, char message[])
{
  int char_write;
  
  char_write = write(socket,message,strlen(message));
  if (char_write < 0)
  {
    error("FEHLER beim Schreiben");
    return -1;
  }
  
  return char_write;
}

/********************************************
 * FUNKTION
 * 
 * Name: transmit_tcp_socket(int client_socket, int server_socket, FILE* file)
 * 
 * Zweck: überträgt daten zwischen den beiden übergebenen sockets hin und her
 * 	  bricht bei ablauf des gegebenen timeouts ab
 * 
 * Parameter:
 * 	int client_socket: INPUT, der eine socket
 * 	int server_socket: INPUT, der andere socket
 * 	FILE* file: INPUT, datei, in die die ausgaben geschrieben werden
 * 	int timeout: INPUT, timeout in sekunden
 * 
 * Globale Variablen:
 * 	keine
 * 
 * Rückgabewert:
 * 	tcp_transmit_struct.flag:
 * 	-1 falls ein timeout aufgetreten ist
 * 	0 wenn es nichts zu lesen aber auch keinen timeout gab
 * 	1 bei einem client request
 * 	2 bei einem server response
 * 	tcp_transmit_struct.byte_count:
 * 	anzahl der übetragenen bytes
 * 
 ********************************************/
struct tcp_transmit_struct transmit_tcp_socket(int client_socket, int server_socket, FILE* file, int timeout, int counter_client, int counter_server, char server_host[], unsigned short server_port)
{
  static char buffer[1024];			/* Buffer, in den von den Sockets eingelesen wird */
  struct sockaddr_in request_name;		/* Struct, in den der name des Clients mit getsockname geschrieben wird */
  socklen_t request_name_len = sizeof(request_name);	/* Größe des Structs */
  int char_read_client, char_read_server;	/* Anzahl der Zeichen, die vom jeweiligen Socket gelesen wurden */
  struct timeval tv_timeout;			/* Struct, in dem der timeout angegeben wird */
  int max = 0;					/* Speichert den höchsten Socket Descriptor für select */
  fd_set fds;					/* file descriptor set für select */
  time_t timer;					/* Zur Ausgabe des Zeitstempels */
  int offset = 0;				/* Gib an, wie viele Daten des aktuellen Request/Response schon geschrieben wurden */
  struct tcp_transmit_struct return_value;	/* Rückgabewert, enthält ein flag und die Anzahl der übertragenen Bytes */
  return_value.byte_count = 0;
  return_value.flag = -1;
  
  /* Die sockets non-blocking machen */
  fcntl(client_socket, F_SETFL, O_NONBLOCK);
  fcntl(server_socket, F_SETFL, O_NONBLOCK);
  
  /* die höhere socket id herausfinden, wird für select benötigt */
  if (client_socket > server_socket)
  {
    max = client_socket;
  }
  else
  {
    max = server_socket;
  }
 
  /* timeout und fd_set müssen bei vor jedem aufruf son select neu gesetzt werden,
   * da select dort alles mögliche reinschreiben kann */
  tv_timeout.tv_sec = timeout;
  tv_timeout.tv_usec = 0;
  
  /* fd_set zurücksetzen */
  FD_ZERO(&fds);
  
  /* sockets dem fd_set hinzufügen */
  FD_SET(client_socket, &fds);
  FD_SET(server_socket, &fds);
  
  /* select ausführen, wartet maximal timeout oder kehrt vorher zurück, wenn der socket was zu lesen hat */
  if (select(max + 1, &fds, NULL, NULL, &tv_timeout) <= 0)
  {
    /* bei errno == 4 wurde select vom nutzer unterbrochen (interrupt), ansonsten ist ein timeout aufgetreten */
    if (errno != 4)
    {
      /* Es gab nichts zu lesen, timeout ist eingetreten */
      printf("TIMEOUT: Client hat die Zeit überschritten (socket descriptor %d)\n", client_socket);
      
      /* Sende dem Client eine Nachricht über den Timeout */
      write_tcp_socket(client_socket, "TIMEOUT: Sie waren zu lange inaktiv!");
    }
    
    return_value.flag = -1;
    return return_value;
  }
  else
  {
    /* Buffer initialisieren */
    bzero(buffer,sizeof(buffer));
    
    /* versuche von beiden sockets zu lesen,
     * der, bei dem ein Wert größer 0 zurückgegeben wird, hat tatsächlich Daten, die gelesen werden können */
    char_read_client = recv(client_socket,buffer,sizeof(buffer), 0);
    char_read_server = recv(server_socket,buffer,sizeof(buffer), 0);
    
    if (char_read_client > 0)
    {
      /* Aktuelle Zeit abfragen, Header schreiben */
      timer = time(NULL);
      getsockname(client_socket, (struct sockaddr*) &request_name, &request_name_len);
      fprintf(file, "=== (%s:%d) REQUEST # %d, %s", inet_ntoa(request_name.sin_addr), ntohs(request_name.sin_port), counter_client, ctime(&timer)); 
    }
    
    if (char_read_server > 0)
    {
      /* Aktuelle Zeit abfragen, Header schreiben */
      timer = time(NULL);
      fprintf(file, "=== (%s:%u) RESPONSE # %d, %s", server_host, server_port, counter_server, ctime(&timer)); 
      
    }
    
    offset = 0;
    while (char_read_client > 0) 
    {
      /* schreibe das gelesene in die datei */
      hex_print(file, buffer, strlen(buffer), offset);
      
      /* Erhöhe den Offset um die Zahl der bereits geschriebenen Zeichen */
      offset = offset + strlen(buffer);
      
      /* client sendet einen punkt und will damit die verbindung beenden */
      if(strcmp(buffer, ".\r\n") == 0 || strcmp(buffer, ".") == 0)
      {
	fprintf(file, "=== length %d byte(s) ===\n\n", offset); 
	
	/* Meldung ausgeben, dass ein Client die Verbindung beendet hat */
	printf("QUIT: Client hat die Verbindung mit einem \".\" beendet (socket descriptor %d)\n", client_socket);
	
        /* Sende dem Client eine Nachricht über den Timeout */
	write_tcp_socket(client_socket, "VERBINDUNG GETRENNT: Sie haben die Verbindung mit einem \".\" getrennt!");
	
	return_value.flag = -1;
	return return_value;
      }
      
      /* Leite die Clientanfrage an den Server weiter */
      write_tcp_socket(server_socket, buffer);

      /* Buffer initialisieren */
      bzero(buffer,sizeof(buffer));
      
      /* Es wird von Socket in den Buffer gelesen
      * in char_read steht die anzahl der gelesenen Zeichen */
      char_read_client = recv(client_socket,buffer,sizeof(buffer), 0);
      if ( char_read_client <= 0)
      {
	fprintf(file, "=== length %d byte(s) ===\n\n\n", offset); 
	return_value.flag = 1;
	return_value.byte_count = offset;
	return return_value;
      }
    }
    
    offset = 0;
    while (char_read_server > 0) 
    {
      /* schreibe das gelesene in die datei */
      hex_print(file, buffer, strlen(buffer), offset);
      
      /* Erhöhe den Offset um die Zahl der bereits geschriebenen Zeichen */
      offset = offset + strlen(buffer);

      /* Leite die Clientanfrage an den Server weiter */
      write_tcp_socket(client_socket, buffer);

      /* Buffer initialisieren */
      bzero(buffer,sizeof(buffer));

      /* Es wird von Socket in den Buffer gelesen
      * in char_read steht die anzahl der gelesenen Zeichen */
      char_read_server = recv(server_socket,buffer,sizeof(buffer), 0);   
      if ( char_read_server <= 0)
      {
	      fprintf(file, "=== length %d byte(s) ===\n\n\n", offset); 
	      return_value.flag = 2;
	      return_value.byte_count = offset;
	      return return_value;
      }
    }
    /* select meldet, dass es was zu lesen gibt, aber keiner der sockets findet etwas zu lesen
     * ignorieren und beim nächsten funktionsaufruf wieder bei select warten */
    return_value.flag = 0;
    return return_value;
  }
}

/********************************************
 * FUNKTION
 * 
 * Name: close_tcp_socket(int socket)
 * 
 * Zweck: schließt den übergebenen socket
 * 
 * Parameter:
 * 	int socket: INPUT, socket, der geschlossen werden soll
 * 
 * Globale Variablen:
 * 	keine
 * 
 * Rückgabewert:
 * 	nicht vorhanden
 * 
 ********************************************/
void close_tcp_socket(int socket)
{
  close(socket);
}
